.. _Структуры:

Структуры
=========

.. index::
    structure

Как уже неоднократно упоминалось ранее в книге, фундаментальные типы
не подходят для представления концепций более высокого уровня. Например, хотя значение типа
типа int подходит для представления времени суток, две переменные int были бы более уместны для представления момента времени.
для представления момента времени: одна для часа, а другая для
минуту.
Структуры - это функция, позволяющая определять новые типы путем объединения уже
существующих других типов. Новый тип определяется ключевым словом struct. По этому
определению, структуры - это типы, определяемые пользователем. Большая часть содержания этой главы
непосредственно применима и к классам. Особенно концепция объединения существующих
типов для определения нового типа.
В этой главе рассмотрены только основные возможности структур. Более подробно мы рассмотрим
структур в следующих главах:

* :ref:`Функции члены<Функции члены>`
* :ref:`Параметры const ref и константные функции-члены<Параметры const ref и константные функции-члены>`
* :ref:`Конструктор и другие специальные функции<Конструктор и другие специальные функции>`
* :ref:`Перегрузка операторов<глава_53_start>`
* :ref:`Инкапсуляция и защита атрибутов<глава_60_start>`
* :ref:`Свойства<Свойства>`
* :ref:`Контрактное программирование для структур и классов<Контрактное программирование для структур и классов>`
* :ref:`foreach со структурами и классами<глава_73_start>`

Чтобы понять, насколько полезны структуры, давайте рассмотрим функцию addDuration()
которую мы определили ранее в главе "Утверждение и обеспечение выполнения" (:ref:`глава assert и enforce<глава assert и enforce>`).
Следующее определение взято из решения упражнения той главы:


.. code-block:: d

    void addDuration(int startHour, int startMinute,
                     int durationHour, int durationMinute,
                    out int resultHour, out int resultMinute)
    {
        resultHour = startHour + durationHour;
        resultMinute = startMinute + durationMinute;
        resultHour += resultMinute / 60;
        resultMinute %= 60;
        resultHour %= 24;
    }

*Примечание: В этой главе я буду игнорировать блоки in, out и unittest, чтобы сохранить краткость кода.
чтобы примеры кода были короткими.*
Хотя приведенная выше функция явно принимает шесть параметров, при рассмотрении трех пар
параметров, то концептуально она принимает только три бита
информации для времени начала, продолжительности и результата

Определение
-----------

Ключевое слово struct определяет новый тип, объединяя переменные, которые связаны между собой
каким-либо образом:

.. code-block:: d

    struct TimeOfDay
    {
        int hour;
        int minute;
    }

Приведенный выше код определяет новый тип TimeOfDay, который состоит из двух
переменных с именами час и минута. Это определение позволяет использовать новый тип TimeOfDay
использоваться в программе так же, как и любой другой тип. Следующий код
демонстрирует, насколько его использование похоже на использование типа int:

.. code-block:: d

    int number;
    number = otherNumber;
    TimeOfDay time;
    time = otherTime;

Синтаксис определения структуры следующий:

.. code-block:: d

    struct TypeName
    {
    // ... переменные и функции члены
    }

С функциями-членами мы познакомимся в последующих главах.
Переменные, которые объединяет структура, называются ее членами. В соответствии с этим
определению, TimeOfDay имеет два члена: hour и minute.

**struct определяет тип, а не переменную**

Здесь есть важное различие: Особенно после глав "Область действия имени" (стр. 90)
и главы "Время жизни и основные операции" (стр. 228), фигурные
скобки в определениях struct могут создать неверное впечатление, что члены struct
начинают и заканчивают свою жизнь внутри этой области видимости. Это не так.
Определения членов не являются определениями переменных:

.. code-block:: d

    struct TimeOfDay
    {
        int hour;   // ← Не является переменной; станет частью
                    // переменной struct, используемой в программе.
        int minute; // ← Не является переменной; станет частью
                    // переменной struct, используемой в программе.
    }

Определение структуры определяет типы и имена членов
которые будут иметь объекты этой структуры. Эти переменные-члены будут
сконструированы как части объекта TimeOfDay, участвующего в программе:

.. code-block:: d

    TimeOfDay bedTime;    // Этот объект содержит свои собственные переменные-члены.
    TimeOfDay wakeUpTime; // Этот объект содержит свои собственные переменные-члены, а также
                          // также. Переменные-члены
                          // этого объекта не связаны с
                          // переменными-членами
                          // предыдущего объекта.

Переменные типов struct и class называются объектами.

**Удобство кодирования**

Возможность объединить понятия часа и минуты в новый тип - это
большое удобство. Например, приведенная выше функция может быть переписана более
более осмысленным образом, принимая три параметра TimeOfDay вместо существующих шести
int:

.. code-block:: d

    void addDuration(TimeOfDay start,
                     TimeOfDay duration,
                     out TimeOfDay result)
    {
      // ...
    }

*Примечание: Не следует добавлять две переменные, которые представляют два момента времени. Для
например, бессмысленно добавлять время обеда 12:00 к времени завтрака 7:30. По адресу
было бы разумнее определить другой тип, правильно названный Duration, и
добавлять объекты этого типа к объектам TimeOfDay. Несмотря на этот недостаток дизайна, я буду продолжать
использовать только объекты TimeOfDay в этой главе и введу Duration в одной из следующих глав.
главе.*

Как вы помните, функции возвращают одно значение. Именно это
именно по этой причине раннее определение функции addDuration() принимало два
параметра: Она не могла вернуть информацию о часах и минутах в виде одного
значение.
Структуры снимают и это ограничение: Поскольку несколько значений могут быть объединены
как один тип struct, функции могут возвращать объект такой структуры,
эффективно возвращая сразу несколько значений. addDuration() теперь может быть определена
как возвращающую результат:

.. code-block:: d

    TimeOfDay addDuration(TimeOfDay start,
                          TimeOfDay duration)
    {
    // ...
    }

Как следствие, addDuration() теперь становится функцией, которая производит значение
значение, в отличие от функции, которая имеет побочные эффекты. Как вы
помните из главы "Функции" (стр. 136), получение результатов предпочтительнее
чем наличие побочных эффектов.
Структуры могут быть членами других структур. Например, следующая структура имеет
два члена TimeOfDay:

.. code-block:: d

    struct Meeting
    {
        string topic;
        size_t attendanceCount;
        TimeOfDay start;
        TimeOfDay end;
    }

Собрание, в свою очередь, может быть членом другой структуры. Предположим, что существует также
структура Meal:

.. code-block:: d

    struct DailyPlan
    {
        Meeting projectMeeting;
        Meal lunch;
        Meeting budgetMeeting;
    }

Доступ к членам
---------------

Члены структуры используются так же, как и любая другая переменная. Единственное отличие заключается в том, что перед именем
перед именем члена должна быть указана фактическая переменная struct и точка:

.. code-block:: d

    start.hour = 10;

Строка выше присваивает значение 10 члену hour объекта start.
Давайте перепишем функцию addDuration() с учетом того, что мы видели до сих пор:

.. code-block:: d

    TimeOfDay addDuration(TimeOfDay start,
                          TimeOfDay duration)
    {
        TimeOfDay result;
        result.minute = start.minute + duration.minute;
        result.hour = start.hour + duration.hour;
        result.hour += result.minute / 60;
        result.minute %= 60;
        result.hour %= 24;
        return result;
    }

Обратите внимание, что в этой версии функции имена переменных стали намного короче
start, duration и result. Кроме того, вместо использования сложных
имен, таких как startHour, можно обращаться к членам struct через их
соответствующие переменные struct, как в start.hour.
Вот код, в котором используется новая функция addDuration(). Учитывая время начала
и продолжительность, следующий код вычисляет, когда закончится учебный период в школе
закончится:

.. code-block:: d

    void main()
    {
        TimeOfDay periodStart;
        periodStart.hour = 8;
        periodStart.minute = 30;
        TimeOfDay periodDuration;
        periodDuration.hour = 1;
        periodDuration.minute = 15;
        immutable periodEnd = addDuration(periodStart,periodDuration);
        writefln("Period end: %s:%s",  periodEnd.hour, periodEnd.minute);
    }

Вывод:

.. code-block:: console

    Period end: 9:45

Приведенная выше функция main() была написана только на основе того, что мы видели до сих пор. Мы
сделаем этот код еще короче и чище.

Конструирование
---------------

Первые три строки main() посвящены построению объекта periodStart
а следующие три строки посвящены построению объекта periodDuration. В
каждых трех строках кода сначала определяется объект, а затем устанавливаются его часовые и
минут.
Для того чтобы переменная могла использоваться безопасным образом, она должна быть сначала
сначала сконструирована в согласованном состоянии. Поскольку конструирование встречается так часто, существует
специальный синтаксис построения для объектов struct:

.. code-block:: d

    TimeOfDay periodStart = TimeOfDay(8, 30);
    TimeOfDay periodDuration = TimeOfDay(1, 15);

Значения автоматически присваиваются членам в том порядке, в котором они указаны
указаны: Поскольку час определен первым в структуре, значение 8 присваивается
periodStart.hour и 30 - periodStart.minute.
Как мы уже видели в главе "Преобразования типов" (:ref:`Преобразования типов<глава_46_start>`), синтаксис конструкции
можно использовать и для других типов:

.. code-block:: d

    auto u = ubyte(42); // u is a ubyte
    auto i = int(u); // i is an int

**Конструирование объектов как неизменяемых**

Возможность конструировать объект, сразу указывая значения его членов
делает возможным определение объектов как неизменяемых:

.. code-block:: d

    immutable periodStart = TimeOfDay(8, 30);
    immutable periodDuration = TimeOfDay(1, 15);

Иначе было бы невозможно сначала пометить объект как неизменяемый, а затем
модифицировать его члены:

.. code-block:: d

    immutable TimeOfDay periodStart;
    periodStart.hour = 8; // ← compilation ERROR
    periodStart.minute = 30; // ← compilation ERR

**Пропуск членов в конструкторе**

Может быть указано меньше значений, чем количество членов. В этом случае,
оставшиеся члены инициализируются значениями .init соответствующих
типов.
Следующая программа каждый раз конструирует объекты Test с одним меньшим
параметром конструктора. Проверки assert показывают, что неуказанные члены
инициализируются автоматически значениями .init. (Причина необходимости вызова
isNaN() объясняется после программы):

.. code-block:: d

    import std.math;

    struct Test
    {
        char c;
        int i;
        double d;
    }

    void main()
    {
    // The initial values of all of the members are specified
        auto t1 = Test('a', 1, 2.3);
        assert(t1.c == 'a');
        assert(t1.i == 1);
        assert(t1.d == 2.3);
    // Last one is missing
        auto t2 = Test('a', 1);
        assert(t2.c == 'a');
        assert(t2.i == 1);
        assert(isNaN(t2.d));
    // Last two are missing
        auto t3 = Test('a');
        assert(t3.c == 'a');
        assert(t3.i == int.init);
        assert(isNaN(t3.d));
    // No initial value specified
        auto t4 = Test();
        assert(t4.c == char.init);
        assert(t4.i == int.init);
        assert(isNaN(t4.d));
    // The same as above
        Test t5;
        assert(t5.c == char.init);
        assert(t5.i == int.init);
        assert(isNaN(t5.d));
    }

Как вы помните из главы "Типы с плавающей точкой" (стр. 42), начальное значение double
начальное значение double равно double.nan. Поскольку значение .nan не упорядочено, оно
бессмысленно использовать его при сравнении равенств. Вот почему вызов
std.math.isNaN - это правильный способ определить, равно ли значение
.nan.

**Указание значений по умолчанию для членов**

Важно, чтобы переменные-члены автоматически инициализировались известными
начальными значениями. Это предотвращает продолжение работы программы с неопределенными
значениями. Однако значение .init для соответствующих типов может не подходить для
не для каждого типа. Например, char.init вообще не является допустимым значением.
Начальные значения членов структуры можно указать, когда структура
определена. Это полезно, например, для инициализации членов с плавающей точкой значением 0.0,
вместо бесполезного в большинстве случаев значения .nan.
Значения по умолчанию задаются с помощью синтаксиса присваивания, когда члены
определены:

.. code-block:: d

    struct Test
    {
        char c = 'A';
        int i = 11;
        double d = 0.25;
    }

Обратите внимание, что приведенный выше синтаксис на самом деле не является присваиванием. Приведенный выше код просто
определяет значения по умолчанию, которые будут использоваться, когда объекты этой структуры будут
позже в программе.
Например, следующий объект Test строится без каких-либо
конкретных значений:

.. code-block:: d

    Test t; // no value is specified for the members
    writefln("%s,%s,%s", t.c, t.i, t.d);

Все члены инициализируются значениями по умолчанию:

.. code-block:: console

    A,11,0.25

**Конструирование с помощью синтаксиса {}**

Объекты Struct также могут быть построены с помощью следующего синтаксиса:

.. code-block:: d

    TimeOfDay periodStart = { 8, 30 };

Как и в предыдущем синтаксисе, указанные значения присваиваются членам в том
в том порядке, в котором они указаны. При этом отстающие члены получают значения по умолчанию.
Этот синтаксис унаследован от языка программирования C:

.. code-block:: d

    auto periodStart = TimeOfDay(8, 30); // ← regular
    TimeOfDay periodEnd = { 9, 30 }; // ← C-style

Этот синтаксис позволяет использовать назначенные инициализаторы. Назначенные инициализаторы предназначены для указания
члена, с которым связано значение инициализации. Можно даже
инициализировать члены в порядке, отличном от того, в котором они определены в struct:

.. code-block:: d

    TimeOfDay t = { minute: 42, hour: 7 };

Копирование и присваивание
--------------------------

Структуры являются типами значений. Как было описано в главе Типы значений и ссылочные
(стр. 159), это означает, что каждый объект struct имеет свое собственное значение.
Объекты получают свои значения при создании, и их значения меняются, когда
когда им присваиваются новые значения.

.. code-block:: d

    auto yourLunchTime = TimeOfDay(12, 0);
    auto myLunchTime = yourLunchTime;
    // Only my lunch time becomes 12:05:
    myLunchTime.minute += 5;
    // ... your lunch time is still the same:
    assert(yourLunchTime.minute == 0);

При копировании все члены исходного объекта автоматически копируются в
соответствующие члены объекта назначения. Аналогично, присвоение
заключается в присвоении каждого члена источника соответствующему члену
объекта назначения.
Члены структур, которые относятся к ссылочным типам, требуют особого внимания.

**Осторожнее с членами ссылочных типов!**

Как вы помните, копирование или присвоение переменных ссылочных типов не
изменяет значение, а изменяет объект, на который ссылаются. В результате копирование
или присваивание порождает еще одну ссылку на правый объект. На
применительно к членам struct это означает, что члены двух отдельных объектов struct
начнут предоставлять доступ к одному и тому же значению.
Чтобы увидеть пример этого, давайте рассмотрим структуру, один из членов которой
является ссылочным типом. Эта структура используется для хранения номера студента и
оценки студента:

.. code-block:: d

    struct Student
    {
        int number;
        int[] grades;
    }

Следующий код создает второй объект Student путем копирования существующего
один:

.. code-block:: d

    // Constructing the first object:
    auto student1 = Student(1, [ 70, 90, 85 ]);
    // Constructing the second student as a copy of the first
    // one and then changing its number:
    auto student2 = student1;
    student2.number = 2;
    // WARNING: The grades are now being shared by the two objects!
    // Changing the grades of the first student ...
    student1.grades[0] += 5;
    // ... affects the second student as well:
    writeln(student2.grades[0]);

Когда создается student2, его члены получают значения членов student1.
student1. Поскольку int - это тип значения, второй объект получает свое собственное числовое значение.
Оба объекта Student также имеют индивидуальные члены grades.
Однако, поскольку срезы являются ссылочными типами, фактические элементы, которые разделяют два среза
одинаковы. Следовательно, изменение, сделанное в одном из срезов, будет видно
через другой срез.
Вывод кода показывает, что оценка второго ученика была
также повысилась:

.. code-block:: console

    75

По этой причине лучшим подходом может быть построение второго объекта по
копиями оценок первого:

.. code-block:: d

    // The second Student is being constructed by the copies
    // of the grades of the first one:
    auto student2 = Student(2, student1.grades.dup);
    // Changing the grades of the first student ...
    student1.grades[0] += 5;
    // ... does not affect the grades of the second student:
    writeln(student2.grades[0]);

Поскольку оценки были скопированы с помощью .dup, на этот раз оценки второго
не пострадали:

.. code-block:: console

    70

*Примечание: Можно автоматически скопировать даже члены ссылки. Мы увидим
как это делается позже, когда будем рассматривать функции-члены struct.*

Литералы структур
-----------------

Подобно возможности использовать в выражениях целочисленные литеральные значения, например 10, без
без необходимости определять переменную, объекты struct также можно использовать в качестве литералов.
Литералы struct строятся с помощью синтаксиса построения объектов.

.. code-block:: d

    TimeOfDay(8, 30) // ← struct literal value

Давайте сначала перепишем приведенную выше функцию main() с учетом того, что мы узнали с момента ее
последней версии. Переменные строятся с помощью синтаксиса конструирования и на этот раз являются неизменяемыми.

.. code-block:: d

    void main()
    {
        immutable periodStart = TimeOfDay(8, 30);
        immutable periodDuration = TimeOfDay(1, 15);
        immutable periodEnd = addDuration(periodStart,  periodDuration);
        writefln("Period end: %s:%s",
        periodEnd.hour, periodEnd.minute);
    }

Обратите внимание, что periodStart и periodDuration не обязательно должны быть определены как именованные
переменные в приведенном выше коде. На самом деле это временные переменные в этой простой
которые используются только для вычисления переменной periodEnd. Их можно
передать в addDuration() в качестве буквальных значений:

.. code-block:: d

    void main()
    {
        immutable periodEnd = addDuration(TimeOfDay(8, 30), TimeOfDay(1, 15));
        writefln("Period end: %s:%s",periodEnd.hour, periodEnd.minute);
    }

Cтатические члены
-----------------

Хотя объектам в основном нужны отдельные копии членов структуры,
иногда имеет смысл, чтобы объекты определенного типа struct совместно использовали некоторые
переменные. Это может быть необходимо, например, для поддержания общей информации о
данном типе структуры.
В качестве примера представим себе тип, который присваивает отдельный идентификатор каждому
объекта, построенного по этому типу:

.. code-block:: d

    struct Point
    {
    // The identifier of each object
        size_t id;
        int line;
        int column;
    }

Чтобы иметь возможность присваивать разные идентификаторы каждому объекту, необходима отдельная переменная
для хранения следующего доступного идентификатора. Она будет увеличиваться каждый раз, когда
объект. Предположим, что nextId будет определена в другом месте и будет
доступна в следующей функции:

.. code-block:: d

    Point makePoint(int line, int column)
    {
        size_t id = nextId;
        ++nextId;
        return Point(id, line, column);
    }

Необходимо принять решение о том, где должна быть определена общая переменная nextId.
В таких случаях полезно использовать статические члены.
Такая общая информация определяется как статический член структуры.
В отличие от обычных членов, для каждого статического члена существует единственная переменная
для каждого потока. (Обратите внимание, что большинство программ состоит из одного потока, который
запускает выполнение функции main().) Эта единственная переменная является общей для всех
объектов этой структуры в данном потоке:

.. code-block:: d

    import std.stdio;

    struct Point
    {
    // The identifier of each object
        size_t id;
        int line;
        int column;
    // The id of the next object to construct
        static size_t nextId;
    }

    Point makePoint(int line, int column)
    {
        size_t id = Point.nextId;
        ++Point.nextId;
        return Point(id, line, column);
    }

    void main()
    {
        auto top = makePoint(7, 0);
        auto middle = makePoint(8, 0);
        auto bottom = makePoint(9, 0);
        writeln(top.id);
        writeln(middle.id);
        writeln(bottom.id);
    }

Поскольку nextId увеличивается при каждом создании объекта, каждый объект получает уникальный
id:

.. code-block:: console

    0
    1
    2

Поскольку статические члены принадлежат всему типу, не обязательно иметь объект
для доступа к ним. Как мы видели выше, к таким объектам можно обращаться как по имени
типа, а также по имени любого объекта этой структуры:

.. code-block:: d

    ++Point.nextId;
    ++bottom.nextId; // would be the same as above

Если переменная нужна не одна на поток, а одна на программу, то такие переменные
переменные должны быть определены как общие статические. С ключевым словом shared мы познакомимся в
в одной из следующих глав.

.. index::
    static this
    static ~this

**static this() для инициализации и static ~this() для завершения работы**

Вместо того чтобы явно присваивать nextId начальное значение, мы использовали его
начальное значение по умолчанию, ноль. Мы могли бы использовать любое другое значение:

.. code-block:: d

    static size_t nextId = 1000;

Однако такая инициализация возможна только в том случае, если начальное значение известно во время
время компиляции. Кроме того, может потребоваться выполнить специальный код, прежде чем структура
может быть использована в потоке. Такой код записывается в статических областях this().
Например, следующий код считывает начальное значение из файла, если этот файл
существует:

.. code-block:: d

    import std.file;

    struct Point
    {
        // ...
        enum nextIdFile = "Point_next_id_file";
        static this()
        {
            if (exists(nextIdFile))
            {
                auto file = File(nextIdFile, "r");
                file.readf(" %s", &nextId);
            }
        }
    }


Содержимое статических блоков this() автоматически выполняется один раз для каждого
до того, как тип struct будет использован в этом потоке. Код, который должен быть
должен выполняться только один раз для всей программы (например, инициализация разделяемых и неизменяемых
переменных) должен быть определен в блоках shared static this() и shared static
~this(), которые будут рассмотрены в главе "Совместное использование данных" (Data Sharing Concurrency).(:ref:`Совместное использование данных (Concurrency)<Совместное использование данных (Concurrency)>`).
Аналогично, static ~this() предназначен для завершающих операций потока, а shared  static ~this() - для завершающих операций всей программы.
Следующий пример дополняет предыдущую статическую функцию this(), записывая
значение nextId в тот же файл, эффективно сохраняя идентификаторы объектов в течение
последовательных выполнениях программы:

.. code-block:: d

    struct Point
    {
        // ...
        static ~this()
        {
            auto file = File(nextIdFile, "w");
            file.writeln(nextId);
        }
    }

Теперь программа будет инициализировать nextId с того места, на котором она остановилась. Например,
результатом второго выполнения программы будет следующее:

.. code-block:: console

    3
    4
    5

Упражнения
-----------
#. Создайте структуру с именем Card для представления игральной карты.
Эта структура может иметь два члена - для масти и значения. Возможно, имеет смысл
использовать перечисление для представления масти, или вы можете просто использовать символы
символы ????
Для значения карты можно использовать значение int или dchar. Если вы решите использовать
int, то значения 1, 11, 12 и 13 могут представлять карты, которые не имеют
номера (туз, валет, королева и король).
Существуют и другие варианты дизайна. Например, значения карт могут быть
например, значения карт могут быть представлены типом enum.
То, как будут построены объекты этой структуры, будет зависеть от выбора
типов ее членов. Например, если оба члена - dchar, то
Объекты Card могут быть построены следующим образом:

.. code-block:: d

    auto card = Card('?', '2');

#. Определите функцию printCard(), которая принимает объект Card в качестве
и просто печатает его:

.. code-block:: d

    struct Card
    {
        // ... please define the struct ...
    }

    void printCard(Card card)
    {
        // ... please define the function body ...
    }

    void main()
    {
        auto card = Card( /* ... */ );
        printCard(card);
    }


Например, функция может вывести 2 трефы в виде:

.. code-block:: console

    &???2

#. Определите функцию newDeck(), и пусть она возвращает 52 карты из колоды в виде

.. code-block:: d

    Card[] newDeck()
    out (result)
    {
        assert(result.length == 52);
    }
    do
    {
        // ... please define the function body ...
    }


Должно быть возможно вызвать newDeck(), как показано в следующем коде:

.. code-block:: d

    Card[] deck = newDeck();
    foreach (card; deck)
    {
        printCard(card);
        write(' ');
    }

    writeln();

Результат должен быть похож на следующий с 52 различными картами:

.. code-block:: console

    ??????

#. Напишите функцию, которая перетасовывает колоду. Один из способов - выбрать две карты
случайным образом с помощью std.random.uniform, поменять эти две карты местами и повторить этот процесс
процесс достаточное количество раз. Функция должна принимать число
повторений в качестве параметра:

.. code-block:: d

    void shuffle(Card[] deck, int repetition)
    {
    // ... please define the function body ...
    }

Вот как его следует использовать:

.. code-block:: d

    Card[] deck = newDeck();
    shuffle(deck, 1);
    foreach (card; deck)
    {
        printCard(card);
        write(' ');
    }
    writeln();


Функция должна менять местами повторяющиеся карточки несколько раз. Например,
при вызове на 1 вывод должен быть похож на следующий:

.. code-block:: console

    ?????

При большем значении повторения колода будет перетасована сильнее:
shuffled(deck, 100);

Вывод

.. code-block:: console

    ?????
